package com.generic.mybatis.dal.interceptor.automap;

import com.generic.mybatis.dal.dao.annotation.Join;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * 描述java对象的数据库映射信息（数据库表的映射、字段的映射）
 */
public class TableMapper {
    @Resource
    private SqlSessionFactory sqlSessionFactory;

    private String tableName;

    private Class dtoClass;

    private Map<String, FieldMapper> fieldMapperCache;

    private List<String> ids = Lists.newArrayList();

    private Set<String> idset = Sets.newHashSet();

    private List<JoinInfo> joinInfos = Lists.newArrayList();

    private Map<String, JoinInfo> jobInfoMap = Maps.newHashMap();

    private List<PkInfo> pkInfos = Lists.newArrayList();

    private Map<String, PkInfo> pkMap = Maps.newHashMap();

    private ResultMap resultMap = null;

    public String getTableName() {
        return tableName;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public Map<String, FieldMapper> getFieldMapperCache() {
        return fieldMapperCache;
    }

    public void setFieldMapperCache(Map<String, FieldMapper> fieldMapperCache) {
        this.fieldMapperCache = fieldMapperCache;
    }

    public void addId(String name) {
        ids.add(name);
        idset.add(name);
    }

    public void addJoinInfo(Field field, Join join) {
        JoinInfo joinInfo = new JoinInfo(this, field, join);
        joinInfos.add(joinInfo);
        jobInfoMap.put(field.getName(), joinInfo);
    }

    public void addPkInfo(Field field) {
        PkInfo pkInfo = new PkInfo(this, field);
        pkInfos.add(pkInfo);
        pkMap.put(field.getName(), pkInfo);
    }

    public String getAlias() {
        return getDtoClass().getSimpleName().toLowerCase();
    }

    public List<String> getIds() {
        return ids;
    }

    public Class getDtoClass() {
        return dtoClass;
    }

    public void setDtoClass(Class dtoClass) {
        this.dtoClass = dtoClass;
    }

    public List<JoinInfo> getJoinInfos() {
        return joinInfos;
    }

    public List<PkInfo> getPkInfos() {
        return pkInfos;
    }

    /**
     * field format select aaa.bbb as $aaa_bbb
     *
     * @param sql
     * @return
     */
    public SQL buildSelectStatement(SQL sql) {
        for (FieldMapper fieldMapper : getFieldMapperCache().values()) {
            sql.SELECT(String.format("%s.%s as %s", getAlias(),
                    fieldMapper.getDbFieldName(), fieldMapper.getAlias()));
        }
        if (!CollectionUtils.isEmpty(joinInfos)) {
            for (JoinInfo joinInfo : joinInfos) {
                joinInfo.buildSelectStatement(sql);
            }
        }
        return sql;
    }

    public ResultMap getResultMap() {
        if (resultMap == null) {
            resultMap = buildResultMap();
        }
        return resultMap;
    }

    public void buildFromStatement(SQL sql) {
        if (getPkInfos() != null) {
            for (PkInfo pkInfo : getPkInfos()) {
                pkInfo.buildPkStatement(sql);
            }
        }
        if (getJoinInfos() != null) {
            for (JoinInfo joinInfo : getJoinInfos()) {
                joinInfo.buildJoinStatement(sql);
            }
        }
        sql.FROM(String.format("%s %s", getTableName(), getAlias()));
    }

    private ResultMap buildResultMap() {
        String id = "temp";
        ResultMap.Builder builder = new ResultMap.Builder(sqlSessionFactory.getConfiguration(),
                id, getDtoClass(), buildResultMappings());
        resultMap = builder.build();
        return resultMap;
    }

    private List<ResultMapping> buildResultMappings() {
        List<ResultMapping> mappings = new ArrayList<>();
        //ids
        for (String id : ids) {
            FieldMapper fm = fieldMapperCache.get(id);
            ResultMapping.Builder builder = new ResultMapping.Builder(sqlSessionFactory.getConfiguration(),
                    fm.getFieldName(), fm.getAlias(), fm.getField().getType());
            mappings.add(builder.build());
        }
        //property
        for (FieldMapper fm : fieldMapperCache.values()) {
            if (!idset.contains(fm.getFieldName())) {
                ResultMapping.Builder builder = new ResultMapping.Builder(sqlSessionFactory.getConfiguration(),
                        fm.getFieldName(), fm.getAlias(), fm.getField().getType());
                mappings.add(builder.build());
            }
        }
        //nest result
        for (JoinInfo joinInfo : joinInfos) {
            mappings.addAll(joinInfo.buildResultMappings());
        }
        for (PkInfo pkInfo : pkInfos) {
            mappings.addAll(pkInfo.buildResultMappings());
        }
        return mappings;
    }

    public String getPropertyAlias(String property) {
        checkNotNull(property);
        String[] props = property.split("\\.");
        if (props.length == 1) {
            FieldMapper fm = fieldMapperCache.get(property);
            if (fm == null) {
                for (FieldMapper fieldMapper : fieldMapperCache.values()) {
                    if (fieldMapper.getDbFieldName().equals(property)) {
                        fm = fieldMapper;
                        break;
                    }
                }
            }
            checkNotNull(fm);
            return getAlias() + "." + fm.getDbFieldName();
        } else {
            String extPropsName = props[0];
            if (extPropsName.endsWith("PK")) {
                PkInfo pkInfo = pkMap.get(extPropsName);
                checkNotNull(pkInfo);
                return String.format("%s.%s", pkInfo.getTableMapper().getAlias(), props[1]);
            } else {
                JoinInfo joinInfo = jobInfoMap.get(extPropsName);
                checkNotNull(joinInfo);
                return joinInfo.getJoinTableMapper().getPropertyAlias(props[1]);
            }
        }
    }

    public String getPropertyParamName(String property) {
        checkNotNull(property);
        String[] props = property.split("\\.");
        if (props.length == 1) {
            FieldMapper fm = fieldMapperCache.get(property);
            if (fm == null) {
                for (FieldMapper fieldMapper : fieldMapperCache.values()) {
                    if (fieldMapper.getDbFieldName().equals(property)) {
                        fm = fieldMapper;
                        break;
                    }
                }
            }
            checkNotNull(fm);
            return fm.getParameterName();
        } else {
            String extPropsName = props[0];
            if (extPropsName.endsWith("PK")) {
                PkInfo pkInfo = pkMap.get(extPropsName);
                checkNotNull(pkInfo);
                return pkInfo.getPkTableMapper().getPropertyParamName(props[1]);
            } else {
                JoinInfo joinInfo = jobInfoMap.get(extPropsName);
                checkNotNull(joinInfo);
                return joinInfo.getJoinTableMapper().getPropertyParamName(props[1]);
            }
        }
    }


    public Configuration getConfiguration() {
        return sqlSessionFactory.getConfiguration();
    }

}
